/**
 *安装 npm install axios
 首先需要知道：axios不是一种新的技术。

 axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端，本质上也是对原生XHR的封装，只不过它是Promise的实现版本，符合最新的ES规范，有以下特点：

 从浏览器中创建 XMLHttpRequests
 从 node.js 创建 http 请求
 支持 Promise API
 拦截请求和响应
 转换请求数据和响应数据
 取消请求
 自动转换 JSON 数据
 客户端支持防御 XSRF
 */
import Vue from 'vue'
import Config from '@/config'
import axios from 'axios'
import Token from '@/ck/util/token'
import router from '@/router'
import ErrorCode from '@/config/error-code'

/**
 .env 全局默认配置文件，不论什么环境都会加载合并
 .env.development 开发环境下的配置文件
 .env.production 生产环境下的配置文件
 */
//0 全局的 axios 默认值
const config = {
  baseURL: Config.baseURL || process.env.apiUrl || '', // 接口统一域名
  timeout: 10 * 1000, // 请求超时时间设置
  crossDomain: true, // 跨域
  // return true、promise将resolved,设置为null或者undefined将rejected
  validateStatus (status) {//定义可获得的http响应状态码
    return status >= 200 && status < 510
  }
}

function refreshTokenException (code) {
  let flag = false
  const codes = [401, 403]
  if (codes.includes(code)) {
    flag = true
  }
  return flag
}

function message (message) {
  Vue.prototype.$message({
    message,
    type: 'error'
  })
}

//1 创建请求实例
const _axios = axios.create(config)
// ------------------- 一、请求拦截器 忽略
_axios.interceptors.request.use(originConfig => {
  const reqConfig = { ...originConfig }
  // step1: 容错处理
  if (!reqConfig.url) {
    // console.error('request need url')
    throw new Error({
      source: 'axiosInterceptors',
      message: 'request need url'
    })
  }
  if (!reqConfig.method) {
    // 默认使用 post 请求
    reqConfig.method = 'post'
  }
  // 大小写容错 全变成小写
  reqConfig.method = reqConfig.method.toLowerCase()
  // 参数容错
  if (reqConfig.method === 'get') {
    if (!reqConfig.params) {
      // 防止字段用错
      reqConfig.params = reqConfig.data || {}
    }
  } else if (reqConfig.method === 'post') {
    if (!reqConfig.data) {
      reqConfig.data = reqConfig.params || {}
    }
    // 检测是否包含文件类型, 若包含则进行 formData 封装
    let hasFile = false
    Object.keys(reqConfig.data).forEach(key => {
      if (typeof reqConfig.data[key] === 'object') {
        const item = reqConfig.data[key]
        if (item instanceof FileList || item instanceof File || item instanceof Blob) {
          hasFile = true
        }
      }
    })
    // 检测到存在文件使用 FormData 提交数据
    if (hasFile) {
      const formData = new FormData()
      Object.keys(reqConfig.data).forEach(key => {
        formData.append(key, reqConfig.data[key])
      })
      reqConfig.data = formData
    }
  } else {
    // TODO: 其他类型请求数据格式处理
    console.warn(`其他请求类型: ${reqConfig.method}, 暂无自动处理`)
  }

  // TODO step2: permission 处理
  if (reqConfig.url === 'cms/user/refresh') {
    // 刷新时用刷新Token
    const refreshToken = Token.getToken('refresh_token')
    if (refreshToken) {
      reqConfig.headers.Authorization = refreshToken
    }

  } else {
    // 正常请求用 access_token
    const accessToken = Token.getToken('access_token')
    if (accessToken) {
      reqConfig.headers.Authorization = accessToken
    }
  }
  reqConfig.headers.terminalcode = 'Manage'

  return reqConfig
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error)
})
// ----------------- 二、响应拦截器 忽略
_axios.interceptors.response.use(res => {
  console.log(`${res.config.url} \n${res.config.headers.Authorization}\n${res.config.data}`)
  console.log(`本次完整返回res=`, res)
  if (res.status.toString().charAt(0) === '2') {
    let {
      Status,
      Msg
    } = res.data
    // if (Status === '200') {
    //   return res.data.Data
    // }
    if (Status.charAt(0) === '2'){
      return res.config.statusShow ? res.data :res.data.Data
    }
    return new Promise((resolve, reject) => {
      // 第一种情况：默认直接提示后端返回的异常信息；特殊情况：如果本次请求添加了 handleError: true，用户自己try catch，框架不做处理
      if (res.config.handleError) {
        console.log('============', res)
        return reject(res.data)
      }
      // 使用的第一种补充 自己不处理 且 有状态码的采用前端自己的一套异常提示信息
      const errorArr = Object.entries(ErrorCode).filter(v => v[0] === Status.toString())
      if (errorArr.length > 0 && errorArr[0][1] !== '') {
        Msg = errorArr[0][1]
      }

      // 本次未使用 第二种情况：采用前端自己的一套异常提示信息；特殊情况：如果本次请求添加了 showBackend: true, 弹出后端返回错误信息。
      // if (Config.useFrontEndErrorMsg && !res.config.showBackend){
      //   Msg = ErrorCode['777']
      // }
      message(Msg)
      reject()
    })
  }
  return new Promise((resolve, reject) => {

    // refreshToken相关，直接登出
    if (refreshTokenException(res.status)) {
      // setTimeout(() => {
      //   message('登录过期请重新登录')
      //   router.push('/login')
      // }, 1500)
      return resolve(null)
    }
    // TODO 刷新令牌
    // 其他异常
    message(`${res.status} 服务器未知错误`)
    reject()
  })

}, error => {
  if (!error.response) {
    Vue.prototype.$notify({
      title: 'Network Error',
      dangerouslyUseHTMLString: true,
      message: '<strong class="my-notify">请检查 网络 是否异常</strong>',
    })
    console.log('error', error)
  }
  // 判断请求超时
  if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
    Vue.prototype.$message({
      type: 'warning',
      message: '请求超时',
    })
  }
  return Promise.reject(error)
})

// vue 插件
Plugin.install = function (Vue, options) {
  Vue.axios = _axios
  window.axios = _axios
  //Object.defineProperties() 方法直接在一个对象上定义新的属性或修改多个现有属性，并返回该对象。 添加实例方法
  //给Vue.prototype添加实例方法
  Object.defineProperties(Vue.prototype, {
    axios: {
      get () {
        return _axios
      },
    },
    $axios: {
      get () {
        return _axios
      },
    },
  })
}
//通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成
if (!Vue.axios) {
  Vue.use(Plugin)
}

// 导出常用函数

/**
 * @param {string} url
 * @param {object} data
 * @param {object} params
 */
export function post (url, data = {}, params = {}) {
  return _axios({
    method: 'post',
    url,
    data,
    params
  })
}

/**
 * @param {string} url
 * @param {object} params
 */
export function get (url, params = {}) {
  return _axios({
    method: 'get',
    url,
    params
  })
}

/**
 * @param {string} url
 * @param {object} data
 * @param {object} params
 */
export function put (url, data = {}, params = {}) {
  return _axios({
    method: 'put',
    url,
    params,
    data
  })
}

/**
 * @param {string} url
 * @param {object} params
 */
export function _delete (url, params = {}) {
  return _axios({
    method: 'delete',
    url,
    params
  })
}

export default _axios
